﻿using System;
using System.Linq;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;

namespace SimpleHttpServer.Handler
{
	public interface IFormatToString
	{
		string ToString(int level);
	}

	public class StringFormatter
	{
		private int _level = 0;

		private int _indent = 4;

		public StringFormatter(int level, int indent)
		{
			this._level = level;
			this._indent = indent;
		}

		private void AppendSpace(StringBuilder sb, int level)
		{
			int num = level * _indent;
			for (int i = 0; i < num; i++)
			{
				sb.Append(' ');
			}
		}

		private bool IsInternalMethod(MethodInfo method)
		{
			if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
			{
				return true;
			}

			string[] names = {"ToString", "Equals", "GetHashCode", "GetType" };
			return names.Any(name => name == method.Name);
		}

		private bool IsBaseType(Type type)
		{
			if (type.IsPrimitive || type == typeof(string))
			{
				return true;
			}

			return false;
		}

		private string TypeToStr(Type type)
		{
			if (type.ToString().IndexOf('.') >= 0)
			{
				int idx = type.ToString().LastIndexOf('.');
				return type.ToString().Substring(idx + 1);
			}

			return type.ToString();
		}

		private string FormatProperty(object prop, int curLevel)
		{
			if (prop is IFormatToString fts)
			{
				return Environment.NewLine + fts.ToString(curLevel + 1);
			}

			try
			{
				string json = JsonConvert.SerializeObject(prop, Formatting.Indented);
				if (json.StartsWith("{") || json.StartsWith("["))
				{
					StringBuilder sb = new StringBuilder();
					string[] lines = json.Split(new []{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);
					for (var i = 0; i < lines.Length; i++)
					{
						if (i > 0)
						{
							AppendSpace(sb, curLevel + 1);
						}

						sb.Append(lines[i]);
						if (i < lines.Length - 1)
						{
							sb.Append(Environment.NewLine);
						}
					}
					return sb.ToString();
				}

				return json;
			}
			catch (System.Exception)
			{
				string res = prop.ToString();
				if (res == prop.GetType().ToString())
				{
					Type type = prop.GetType();
					int idx = type.ToString().LastIndexOf('.');
					return type.ToString().Substring(idx + 1);
				}

				return res;
			}
		}

		private string FormatMethod(MethodInfo method)
		{
			StringBuilder sb = new StringBuilder(method.Name, 100);
			sb.Append('(');
			foreach (ParameterInfo parameter in method.GetParameters())
			{
				sb.Append($"{TypeToStr(parameter.ParameterType)} {parameter.Name}, ");
			}

			if (sb[sb.Length - 1] == ' ')
			{
				sb.Remove(sb.Length - 2, 2);
			}

			sb.Append($"): {TypeToStr(method.ReturnType)}");
			return sb.ToString();
		}

		public string FormatToString(object obj)
		{
			StringBuilder sb = new StringBuilder();
			int level = _level;
			AppendSpace(sb, level);
			sb.Append("{" + Environment.NewLine);
			level++;
			AppendSpace(sb, level);
			sb.Append("[property] = {" + Environment.NewLine);
			level++;
			foreach (PropertyInfo property in obj.GetType().GetProperties())
			{
				AppendSpace(sb, level);
				object propVal = property.GetValue(obj);
				if (propVal == null) propVal = "null";
				sb.Append($"{property.Name}: {(IsBaseType(propVal.GetType()) ? propVal : FormatProperty(propVal, level))}" + Environment.NewLine);
			}
			level--;
			AppendSpace(sb, level);
			sb.Append("}," + Environment.NewLine);
			AppendSpace(sb, level);
			sb.Append("[method] = [" + Environment.NewLine);
			level++;
			foreach (MethodInfo method in obj.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
			{
				if (IsInternalMethod(method)) continue;
				AppendSpace(sb, level);
				sb.Append($"{FormatMethod(method)}," + Environment.NewLine);
			}

			level--;
			AppendSpace(sb, level);
			sb.Append("]" + Environment.NewLine);
			level--;
			AppendSpace(sb, level);
			sb.Append('}');
			return sb.ToString();
		}
	}
}
